namespace DependencyLib
{
  using System;
  using System.Linq;
  using System.Collections.Generic;
  using System.Reflection;
  using System.IO;
  using System.Collections;
  using System.Xml.Linq;
  public interface Iterable
  {
    System.Collections.IEnumerator GetEnumerator();
  }

  public interface ICLRAssemblyIterator : IEnumerable
  {
    IEnumerable<Assembly> GetAssemblies();
    IEnumerable<AssemblyName> GetAssemblyNames();
  }

  public delegate void OnFileIncludedEventHandler<T>(T assembly, FileInfo file);
  public delegate void OnFileExcludedEventHandler(FileInfo file);
  public delegate void OnFileNotFoundEventHandler(FileInfo file, FileNotFoundException ex);
  public delegate void OnFileNotLoadEventHandler(FileInfo file, FileLoadException ex);
  public delegate void OnBadImageFormatEventHandler(FileInfo file, BadImageFormatException ex);

  public abstract class FileIteratorBase<T> : Iterable
  {
    private DirectoryInfo root_folder_path;
    private string filter;
    private bool recurse_tree;
    private IEnumerable<string> exclude, folder_exclude;

    //public FileIteratorBase(string path, string filter, IEnumerable<string> exclude, IEnumerable<string> folder_exclude, bool tree) : this(new DirectoryInfo(path), filter, exclude, folder_exclude, tree) { }

    public FileIteratorBase(DirectoryInfo path, string filter, IEnumerable<string> exclude, IEnumerable<string> folder_exclude, bool tree)
    {
      this.root_folder_path = path;
      this.filter = filter;
      this.exclude = exclude;
      this.folder_exclude = folder_exclude;
      this.recurse_tree = tree;
      if (!Directory.Exists(this.root_folder_path.FullName))
        throw new DirectoryNotFoundException(this.root_folder_path.FullName);
    }

    public event OnFileNotFoundEventHandler OnFileNotFound;
    public event OnFileNotLoadEventHandler OnFileNotLoad;
    public event OnBadImageFormatEventHandler OnBadImageFormat;
    public event OnFileIncludedEventHandler<T> OnFileIncluded;
    public event OnFileExcludedEventHandler OnFileExcluded;

    protected abstract T GetNextFileElement(string fullName);

    #region Iterable Members

    public System.Collections.IEnumerator GetEnumerator()
    {
      string current_dir = Directory.GetCurrentDirectory();
      try
      {
        SearchOption search_scope = this.recurse_tree ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
        bool is_next_file = false;
        Directory.SetCurrentDirectory(this.root_folder_path.FullName);
        foreach (FileInfo file in this.root_folder_path.GetFiles(this.filter, search_scope))
        {
          if (AssemblyExclusion.IsExcluded(file, this.exclude, this.folder_exclude))
          {
            NotifyFileExcluded(file);
            continue;
          }
          T next_file = default(T);
          is_next_file = false;
          try
          {
            next_file = GetNextFileElement(file.FullName);
            is_next_file = true;
          }
          catch (FileNotFoundException ex) { NotifyFileNotFound(file, ex); }
          catch (FileLoadException ex) { NotifyFileNotLoad(file, ex); }
          catch (BadImageFormatException ex) { NotifyBadImageFormat(file, ex); }
          if (is_next_file)
          {
            NotifyFileIncluded(next_file, file);
            yield return next_file;
          }
        }
      }
      finally
      {
        Directory.SetCurrentDirectory(current_dir);
      }
    }

    #endregion

    #region Notifiers

    protected void NotifyFileIncluded(T assembly, FileInfo file)
    {
      if (OnFileIncluded != null)
        OnFileIncluded(assembly, file);
    }
    protected void NotifyFileExcluded(FileInfo file)
    {
      if (OnFileExcluded != null)
        OnFileExcluded(file);
    }
    protected void NotifyFileNotFound(FileInfo file, FileNotFoundException ex)
    {
      if (OnFileNotFound != null)
        OnFileNotFound(file, ex);
    }
    protected void NotifyFileNotLoad(FileInfo file, FileLoadException ex)
    {
      if (OnFileNotLoad != null)
        OnFileNotLoad(file, ex);
    }
    protected void NotifyBadImageFormat(FileInfo file, BadImageFormatException ex)
    {
      if (OnBadImageFormat != null)
        OnBadImageFormat(file, ex);
    }
    #endregion
  }

  public class AssemblyFolderIterator : FileIteratorBase<Package>
  {
    public AssemblyFolderIterator(string path, string filter, IEnumerable<string> exclude, IEnumerable<string> folder_exclude, bool tree) : this(new DirectoryInfo(path), filter, exclude, folder_exclude, tree) { }
    public AssemblyFolderIterator(DirectoryInfo path, string filter, IEnumerable<string> exclude, IEnumerable<string> folder_exclude, bool tree):base(path, filter, exclude, folder_exclude, tree) { }

    protected override Package GetNextFileElement(string fullName)
    {
      Assembly assembly = Assembly.ReflectionOnlyLoadFrom(fullName);//https://msdn.microsoft.com/en-us/library/system.reflection.assembly.reflectiononlyloadfrom(v=vs.110).aspx
      return new AssemblyPackage(assembly);
    }
  }
  public class MSBuildProjectFileFolderIterator : FileIteratorBase<Package>
  {
    public MSBuildProjectFileFolderIterator(DirectoryInfo path, string filter, IEnumerable<string> exclude, IEnumerable<string> folder_exclude, bool tree):base(path, filter, exclude, folder_exclude, tree) { }

    protected override Package GetNextFileElement(string fullName)
    {
      var msbuild = new MSBuildProjectFile(Path.GetFileName(fullName), new StringReader(File.ReadAllText(fullName)));
      return new MSBuildProjectFilePackage(msbuild);
    }
  }
  /*class AssemblyFileElement : IFileElement
  {
    private Assembly assembly;
    public AssemblyFileElement(Assembly real)
    {
      assembly = real;
    }
    public string FullName { get { return assembly.FullName; } }

  }
  class AssemblyNameFileElement : IFileElement
  {
    public string FullName { get { throw new NotImplementedException(); } }
  }
  class MSBuildProjectFileElement : IFileElement
  {
    private MSBuildProjectFile msbuild;
    public MSBuildProjectFileElement(MSBuildProjectFile real)
    {
      msbuild = real;
    }
    public string FullName { get { return msbuild.Name; } }
  }*/

  public static class AssemblyExclusion
  {
    internal static bool IsExcluded(FileInfo file, IEnumerable<string> excludes, IEnumerable<string> folder_excludes)
    {
      return IsExcludedByFileName(file.FullName, excludes) || IsExcludedByFolderName(file, folder_excludes);
    }

    internal static bool IsExcludedByFileName(string name, IEnumerable<string> excludes)
    {
      if (excludes == null) return false;
      foreach (string excluded in excludes)
        if (name.ToLower().StartsWith(excluded.ToLower()))
          return true;
      return false;
    }

    internal static bool IsExcludedByFolderName(FileInfo file, IEnumerable<string> excludes)
    {
      if (excludes == null) return false;
      foreach (string excluded in excludes)
        if (file.Directory.Name.ToLower() == excluded.ToLower() || file.Directory.FullName.ToLower().Contains(string.Format("\\{0}\\", excluded.ToLower())))
            return true;
      return false;
    }

    internal static bool IsExcludedByName(string name, IEnumerable<string> excludes)
    {
      return IsExcludedByFileName(name, excludes);
    }
  }

  public abstract class Package : Iterable
  {
    public abstract string Name { get; }
    public abstract string FullName { get; }
    public abstract IEnumerable<Package> GetEfferentPackages(Dictionary<string, List<string>> mappings);
    public abstract System.Collections.IEnumerator GetEnumerator();
  }

  public class MappedPackage : Package
  {
    private string mapping;
    internal Dictionary<string, Package> Assemblies;
    internal Dictionary<string, Package> AssemblyNames;

    public MappedPackage(string mapping)
    {
      this.mapping = mapping;
      Assemblies = new Dictionary<string, Package>();
      AssemblyNames = new Dictionary<string, Package>();
    }

    public override string Name { get { return mapping; } }
    public override string FullName { get { return Name; } }
    public string LastMappedFullName { get; internal set; }

    public override IEnumerable<Package> GetEfferentPackages(Dictionary<string, List<string>> mappings)
    {
      var package_iterator = new PackageMapper2(this, mappings);
      foreach (Package package in package_iterator.GetAssemblyNameOrMappedPackages())
        yield return package;

      // if efferent couplings are needed from each AssemblyName, then
      // the AssemblyName->Assembly must be loaded and then iterated by GetReferenceAssemblies (GetAssemblyNameOrMappedPackages)
      //var package_iterator = new PackageMapperIterator<AssemblyName>(this, mappings);
      //foreach (Package package in package_iterator.GetAssemblyNameOrMappedPackages())
      //    yield return package;
    }

    #region Iterable Members

    public override System.Collections.IEnumerator GetEnumerator()
    {
      foreach (AssemblyPackage assembly in this.Assemblies.Values)
        foreach (AssemblyName efferent in assembly.assembly.GetReferencedAssemblies())
          yield return new AssemblyNamePackage(efferent.Name,efferent.FullName);
    }

    #endregion
  }

  public class AssemblyPackage : Package
  {
    internal Assembly assembly;
    public AssemblyPackage(Assembly assembly) { this.assembly = assembly; }
    public Assembly Assembly { get { return assembly; } }
    public override string Name { get { return this.Assembly.GetName().Name; } }
    public override string FullName { get { return this.Assembly.GetName().FullName; } }

    public override IEnumerable<Package> GetEfferentPackages(Dictionary<string, List<string>> mappings)
    {
      var package_iterator = new PackageMapper2(this, mappings);
      var reported_efferents = new HashSet<Package>();
      foreach (Package package in package_iterator.GetAssemblyNameOrMappedPackages())
      {
        if (reported_efferents.Contains(package)) continue;
        reported_efferents.Add(package);
        yield return package;
      }
    }

    #region Iterable Members

    public override System.Collections.IEnumerator GetEnumerator()
    {
      foreach (AssemblyName efferent in this.assembly.GetReferencedAssemblies())
        yield return new AssemblyNamePackage(efferent.Name,efferent.FullName);
    }

    #endregion
  }

  public class AssemblyNamePackage : Package
  {
    //private AssemblyName name;
    private string name,fullname;
    public AssemblyNamePackage(string name, string fullname) { this.name = name; this.fullname = fullname; }
    public override string Name { get { return name; } }
    public override string FullName { get { return fullname; } }
    //public AssemblyName AssemblyName { get { return name; } }
    public override IEnumerable<Package> GetEfferentPackages(Dictionary<string, List<string>> mappings)
    {
      yield return null;
    }

    #region Iterable Members

    public override System.Collections.IEnumerator GetEnumerator()
    {
      yield return null;
    }

    #endregion
  }
  public class MSBuildProjectFilePackage : Package
  {
    private MSBuildProjectFile msbuild;
    public MSBuildProjectFilePackage(MSBuildProjectFile msbuild) { this.msbuild = msbuild; }
    public override string Name { get { return msbuild.Name; } }
    public override string FullName { get { return msbuild.Name; } }
    public override IEnumerable<Package> GetEfferentPackages(Dictionary<string, List<string>> mappings)
    {
      var package_iterator = new PackageMapper2(this, mappings);
      var reported_efferents = new HashSet<Package>();
      foreach (Package package in package_iterator.GetAssemblyNameOrMappedPackages())
      {
        if (reported_efferents.Contains(package)) continue;
        reported_efferents.Add(package);
        yield return package;
      }
    }

    #region Iterable Members

    public override System.Collections.IEnumerator GetEnumerator()
    {
      foreach (MSBuildProjectFile.Dependency efferent in this.msbuild.All().Where(d => !(d is MSBuildProjectFile.Content)))
        yield return new AssemblyNamePackage(efferent.Name, efferent.Name);
    }

    #endregion
  }

  /*public class MSBuildProjectFileDependencyPackage : Package
  {
    private MSBuildProjectFile.Dependency dependency;
    public MSBuildProjectFileDependencyPackage(MSBuildProjectFile.Dependency dependency) { this.dependency = dependency; }
    public override string Name { get { return dependency.Name; } }
    public override string FullName { get { return dependency.Name; } }
    public override IEnumerable<Package> GetEfferentPackages(Dictionary<string, List<string>> mappings)
    {
      yield return null;
    }

    #region Iterable Members

    public override System.Collections.IEnumerator GetEnumerator()
    {
      yield return null;
    }

    #endregion
  }*/

  class PackageMap
  {
    private static readonly PackageMap instance;
    static PackageMap()
    {
      instance = new PackageMap();
    }
    internal static PackageMap Instance { get { return instance; } }

    private PackageMap()
    {
      mapped_packages = new Dictionary<string, MappedPackage>();
    }
    private Dictionary<string, MappedPackage> mapped_packages;
    internal Dictionary<string, MappedPackage> Map { get { return mapped_packages; } }

    public MappedPackage GetMappedPackage(string mapping)
    {
      if (!PackageMap.Instance.Map.ContainsKey(mapping))
        PackageMap.Instance.Map.Add(mapping, new MappedPackage(mapping));
      return PackageMap.Instance.Map[mapping];
    }
  }

  /*public class PackageMapper<T>
  {
    private Iterable locator;
    private Dictionary<string, List<string>> mappings;

    public PackageMapper(Iterable locator, Dictionary<string, List<string>> mappings)
    {
      this.locator = locator;
      this.mappings = mappings;
    }

    public IEnumerable<Package> GetScannedAssemblyAndMappedPackages()
    {
      foreach (Assembly assembly in this.locator)
      {
        string mapping = FindFirstPossibleMappingFor(assembly.FullName);
        if (mapping != null)
          yield return GetMapped(mapping, assembly);
        else
          yield return new AssemblyPackage(assembly);
      }
    }

    public IEnumerable<Package> GetAssemblyNameOrMappedPackages()
    {
      foreach (AssemblyName assembly in this.locator)
      {
        string mapping = FindFirstPossibleMappingFor(assembly.FullName);
        if (mapping != null)
          yield return GetMapped(mapping, assembly);
        else
          yield return new AssemblyNamePackage(assembly);
      }
    }

    private MappedPackage GetMapped(string mapping, Assembly assembly)
    {
      MappedPackage mapped = PackageMap.Instance.GetMappedPackage(mapping);
      mapped.Assemblies[assembly.FullName] = assembly;
      mapped.LastMappedFullName = assembly.FullName;
      return mapped;
    }

    private MappedPackage GetMapped(string mapping, AssemblyName assembly_name)
    {
      MappedPackage mapped = PackageMap.Instance.GetMappedPackage(mapping);
      mapped.AssemblyNames[assembly_name.FullName] = assembly_name;
      mapped.LastMappedFullName = assembly_name.FullName;
      return mapped;
    }

    private string FindFirstPossibleMappingFor(string name)
    {
      if (this.mappings == null) return null;
      foreach (string mapping in this.mappings.Keys)
        foreach (string mapped in this.mappings[mapping])
          if (name.ToLower().StartsWith(mapped.ToLower()))
            return mapping;
      return null;
    }
  }*/

  /*public interface IFileElement
  {
    string FullName { get; }
  }*/
  public class PackageMapper2 //where T : IFileElement
  {
    private Iterable locator;
    private Dictionary<string, List<string>> mappings;

    public PackageMapper2(Iterable locator, Dictionary<string, List<string>> mappings)
    {
      this.locator = locator;
      this.mappings = mappings;
    }
    public IEnumerable<Package> GetScannedAssemblyAndMappedPackages()
    {
      foreach (Package assembly in this.locator)
      {
        string mapping = FindFirstPossibleMappingFor(assembly.FullName);
        if (mapping != null)
          yield return GetMapped(mapping, assembly);
        else
          yield return assembly;
      }
    }

    public IEnumerable<Package> GetAssemblyNameOrMappedPackages()
    {
      foreach (AssemblyNamePackage assembly in this.locator)
      {
        string mapping = FindFirstPossibleMappingFor(assembly.FullName);
        if (mapping != null)
          yield return GetMapped(mapping, assembly);
        else
          yield return assembly;
          //yield return new AssemblyNamePackage(assembly);
      }
    }

    private MappedPackage GetMapped(string mapping, Package assembly)
    {
      MappedPackage mapped = PackageMap.Instance.GetMappedPackage(mapping);
      mapped.Assemblies[assembly.FullName] = assembly;
      mapped.LastMappedFullName = assembly.FullName;
      return mapped;
    }

    private MappedPackage GetMapped(string mapping, AssemblyNamePackage assembly_name)
    {
      MappedPackage mapped = PackageMap.Instance.GetMappedPackage(mapping);
      mapped.AssemblyNames[assembly_name.FullName] = assembly_name;
      mapped.LastMappedFullName = assembly_name.FullName;
      return mapped;
    }

    private string FindFirstPossibleMappingFor(string name)
    {
      if (this.mappings == null) return null;
      foreach (string mapping in this.mappings.Keys)
        foreach (string mapped in this.mappings[mapping])
          if (name.ToLower().StartsWith(mapped.ToLower()))
            return mapping;
      return null;
    }
  }

  public class AssemblyAbstraction
  {
    public decimal AbstractTypeCount { get; set; }
    public decimal TypeCount { get; set; }
    public decimal Instability { get; set; }
    public decimal Abstractness { get { if (TypeCount == 0M) throw new Exception("Don't call Abstractness property without checking first TypeCount == 0"); return AbstractTypeCount / TypeCount; } }
    public decimal DistanceToMainSequence { get { return Math.Abs(Abstractness + Instability - 1M); } }

    public AssemblyAbstraction(Assembly assembly)
    {
      try
      {
        this.TypeCount = assembly.GetTypes().Length;
        this.AbstractTypeCount = count_abstracts(assembly);
        System.Diagnostics.Trace.WriteLine(string.Format("\tReflectionTypeLoad OK for {0}", assembly.FullName));
      }
      catch (ReflectionTypeLoadException ex)
      {
        this.TypeCount = 0;
        this.AbstractTypeCount = 0;
        System.Diagnostics.Trace.WriteLine(string.Format("\tReflectionTypeLoadException for {0}", assembly.FullName));
        foreach (Exception e in ex.LoaderExceptions)
        {
          System.Diagnostics.Trace.WriteLine(string.Format("\t\t{0}", e.Message));
          if (e.InnerException != null)
            System.Diagnostics.Trace.WriteLine(string.Format("\t\t\t{0}", e.InnerException.Message));
        }
      }
    }
    private decimal count_abstracts(Assembly assembly)
    {
      decimal count = 0M;
      foreach (Type type in assembly.GetTypes())
        if (type.IsAbstract || type.IsInterface)
          ++count;
      return count;
    }
  }

  // This class is used by means of its derived class PackageDependencyProvider, because of mappings
  public class CLRDependencyProvider : TemplatedBaseDependencyProvider
  {
    private ICLRAssemblyIterator assembly_provider;
    private IEnumerable<string> excludes;

    protected CLRDependencyProvider()
    {
      AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ReflectionOnlyAssemblyResolver;
    }

    public CLRDependencyProvider(ICLRAssemblyIterator locator, IEnumerable<string> excludes) : this()
    {
      this.assembly_provider = locator;
      this.excludes = excludes;
    }

    protected Assembly ReflectionOnlyAssemblyResolver(object sender, ResolveEventArgs args)
    {
      Exception exception = null;
      bool ok = false;
      try
      {
        System.Diagnostics.Trace.Write(string.Format("\tAssemblyResolve for {0}", args.Name));
        Assembly result = Assembly.ReflectionOnlyLoad(args.Name);
        ok = true;
        return result;
      }
      catch (Exception ex)
      {
        ok = false;
        exception = ex;
        return null;
      }
      finally
      {
        if (ok)
          System.Diagnostics.Trace.WriteLine(" Loaded OK");
        else
          System.Diagnostics.Trace.WriteLine(string.Format(" Loaded NOT OK {0}", exception.Message));
      }
    }

    public bool LocalTypeLevelDependency { get; set; }

    // was overrided by PackageDependencyProvider, because of mappings
    public override TemplatedBaseDependencyProvider.DigraphInfo GetDigraphInfoFromCurrentDependencyProvider()
    {
      string name = null;
      object data = null;
      List<VertexInfo> assembly_vertexes = new List<VertexInfo>();
      List<EdgeInfo> assembly_edges = new List<EdgeInfo>();

      foreach (Assembly assembly in assembly_provider.GetAssemblies())
      {
        List<VertexInfo> type_vertexes = null;
        List<EdgeInfo> type_edges = null;
        if (LocalTypeLevelDependency)
        {
          type_vertexes = new List<VertexInfo>();
          type_edges = new List<EdgeInfo>();
          foreach (Type type in assembly.GetTypes()) //TODO: expect System.Reflection.ReflectionTypeLoadException
          {
            type_vertexes.Add(new VertexInfo(type.FullName));
            AddTypeLevelDependencies(type, type_vertexes, type_edges);
          }
        }
        var A = new AssemblyAbstraction(assembly);
        DigraphInfo assembly_vertex = new DigraphInfo(assembly.GetName().Name, A.TypeCount == 0 ? null : A, type_vertexes, type_edges);
        assembly_vertexes.Add(assembly_vertex);
        foreach (AssemblyName target_assembly in assembly.GetReferencedAssemblies())
        {
          if (AssemblyExclusion.IsExcludedByFileName(target_assembly.FullName, this.excludes))
          {
            //NotifyReferenceExcluded(target_assembly);
            continue;
          }
          //NotifyReferenceIncluded(target_assembly);
          AddEfferentEdge(assembly_vertexes, assembly_edges, assembly.GetName().Name, target_assembly.Name);
        }
      }
      return new DigraphInfo(name, data, assembly_vertexes, assembly_edges);
    }

    private void AddTypeLevelDependencies(Type type, List<VertexInfo> type_vertexes, List<EdgeInfo> type_edges)
    {
      //if(is a local/(nested->subgraph) type then add dependency)
      //pending proper dependency at local/nested types and all members
      foreach (Type target_type in type.GetInterfaces())
        AddEfferentEdge(type_vertexes, type_edges, type.FullName, target_type.FullName);
      foreach (ConstructorInfo ctor in type.GetConstructors(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
      {
        foreach (ParameterInfo param in ctor.GetParameters())
          AddEfferentEdge(type_vertexes, type_edges, type.FullName, param.ParameterType.FullName);
        foreach (LocalVariableInfo local in ctor.GetMethodBody().LocalVariables)
          AddEfferentEdge(type_vertexes, type_edges, type.FullName, local.LocalType.FullName);
        foreach (ExceptionHandlingClause exp in ctor.GetMethodBody().ExceptionHandlingClauses)
          AddEfferentEdge(type_vertexes, type_edges, type.FullName, exp.CatchType.FullName);
      }
      foreach (MethodInfo method in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
      {
        foreach (ParameterInfo param in method.GetParameters())
          AddEfferentEdge(type_vertexes, type_edges, type.FullName, param.ParameterType.FullName);
        foreach (LocalVariableInfo local in method.GetMethodBody().LocalVariables)
          AddEfferentEdge(type_vertexes, type_edges, type.FullName, local.LocalType.FullName);
        foreach (ExceptionHandlingClause exp in method.GetMethodBody().ExceptionHandlingClauses)
          AddEfferentEdge(type_vertexes, type_edges, type.FullName, exp.CatchType.FullName);
      }
      foreach (PropertyInfo prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
        AddEfferentEdge(type_vertexes, type_edges, type.FullName, prop.PropertyType.FullName);
      foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
        AddEfferentEdge(type_vertexes, type_edges, type.FullName, field.FieldType.FullName);
      foreach (EventInfo evento in type.GetEvents(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
        AddEfferentEdge(type_vertexes, type_edges, type.FullName, evento.EventHandlerType.FullName);
    }

    protected void AddEfferentEdge(List<VertexInfo> vertexes, List<EdgeInfo> edges, string source, string target)
    {
      //filter duplicates (hash)-> this is done already, right? -> duplicity is handle on TemplatedBaseDependencyProvider.IncrementGraph
      vertexes.Add(new VertexInfo(source));
      vertexes.Add(new VertexInfo(target));
      edges.Add(new EdgeInfo(null, source, target, null));
    }

    #region IDisposable Members

    public override void Dispose()
    {
      AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= ReflectionOnlyAssemblyResolver;
    }

    #endregion
  }

  /// <summary>
  /// Traverse the PackageMapperIterator, which renders logical packages, and builds the DAG.
  /// Pending: composite metrics
  /// </summary>
  public class PackageDependencyProvider : CLRDependencyProvider
  {
    private PackageMapper2 package_provider;
    private IEnumerable<string> excludes;
    private Dictionary<string, List<string>> mappings;

    public PackageDependencyProvider(PackageMapper2 locator, IEnumerable<string> excludes, Dictionary<string, List<string>> mappings)
    {
      this.package_provider = locator;
      this.excludes = excludes;
      this.mappings = mappings;
    }

    public override TemplatedBaseDependencyProvider.DigraphInfo GetDigraphInfoFromCurrentDependencyProvider()
    {
      string name = null;
      object data = null;
      var result_vertexes = new Dictionary<string,VertexInfo>();
      var result_edges = new List<EdgeInfo>();

      foreach (Package package in package_provider.GetScannedAssemblyAndMappedPackages())
      {
        VertexInfo package_vertex = result_vertexes.ContainsKey(package.Name) ? result_vertexes[package.Name] : new DigraphInfo(package.Name, null, null, null);
        result_vertexes[package.Name]=package_vertex;

        AssemblyPackage assembly_package = package as AssemblyPackage;
        if (assembly_package != null)
        {
          var A = new AssemblyAbstraction(assembly_package.Assembly); //TODO: composite-recursive TypeCount on package not just Assembly
          package_vertex.Data = A.TypeCount == 0 ? null : A;
        }

        MappedPackage mapped = package as MappedPackage;
        if (mapped != null)
          NotifyMappedPackage(mapped);

        foreach (Package efferent_package in package.GetEfferentPackages(this.mappings))
        {
          if (efferent_package.Name == package.Name) continue;
          if (AssemblyExclusion.IsExcludedByName(efferent_package.FullName, this.excludes))
          {
            NotifyReferenceExcluded(efferent_package);
            continue;
          }
          NotifyReferenceIncluded(efferent_package);
          AddEfferentEdgeFrom(result_vertexes, result_edges, package_vertex, efferent_package.Name);
        }
      }
      return new DigraphInfo(name, data, result_vertexes.Values, result_edges);
    }

    protected virtual void AddEfferentEdgeFrom(Dictionary<string, VertexInfo> vertexes, IList<EdgeInfo> edges, VertexInfo source_vertex, string target_name)
    {
      edges.Add(new EdgeInfo(null, source_vertex.Name, target_name, null));
      if(vertexes.ContainsKey(target_name)) return;
      vertexes[target_name] = new VertexInfo(target_name);
    }
  }

  public class MSBuildProjectFile
  {
    #region Nested classes
    public class Dependency
    {
      protected XElement xml;
      public Dependency(XElement source)
      {
        xml = source;
      }
      public virtual string Name { get { return xml.Name.LocalName; } }
    }
    public class Reference : Dependency
    {
      public Reference(XElement source) : base(source) { }
      public override string Name
      {
        get
        {
          var include = xml.Attribute("Include").Value;//TODO input validation
          int index = include.IndexOf(',');
          return index > 0 ? include.Substring(0, index) : include;//TODO option switch to Display Name instead of Simple Name
        }
      }
    }
    public class ProjectReference : Dependency
    {
      public ProjectReference(XElement source) : base(source) { }
      public override string Name { get { return Path.GetFileName(xml.Attribute("Include").Value); } }
    }
    public class Content : Dependency
    {
      public Content(XElement source) : base(source) { }
      public override string Name { get { return Path.GetFileNameWithoutExtension(xml.Attribute("Include").Value); } }
    }
    #endregion

    public static readonly XNamespace NS;

    static MSBuildProjectFile()
    {
      NS = "http://schemas.microsoft.com/developer/msbuild/2003";
    }

    private XDocument doc;
    private List<Dependency> efferent;
    public MSBuildProjectFile(string name, TextReader textReader)
    {
      Init(name, textReader);
    }

    public bool IsWellFormedXML { get; private set; }
    public string Name { get; private set; }

    public IEnumerable<Dependency> All()
    {
      IEnumerable<Dependency> result = efferent;
      return result;
    }
    public IEnumerable<MSBuildProjectFile.ProjectReference> ProjectReferences()
    {
      return efferent.OfType<MSBuildProjectFile.ProjectReference>();
    }
    public IEnumerable<MSBuildProjectFile.Reference> References()
    {
      return efferent.OfType<MSBuildProjectFile.Reference>();
    }
    public IEnumerable<MSBuildProjectFile.Content> Contents()
    {
      return efferent.OfType<MSBuildProjectFile.Content>();
    }

    public Assembly GetProxyAssembly()
    {
      if (!IsWellFormedXML)
      {
        throw new InvalidOperationException("The content is not well-formed XML.");
      }
      Assembly result = null;

      System.CodeDom.Compiler.CodeDomProvider provider = new Microsoft.CSharp.CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v4.0" } });
      var options = new System.CodeDom.Compiler.CompilerParameters();
      options.GenerateExecutable = false;
      options.IncludeDebugInformation = false;
      //foreach (string assemblyname in efferent.Select(e => e.Name))
      //{
      //  options.ReferencedAssemblies.Add(assemblyname);
      //}
      string GeneratedName = Name.Replace(' ', '_').Replace('.', '_');
      if (string.IsNullOrEmpty(GeneratedName))
      {
        options.GenerateInMemory = true;
      }
      else
      {
        options.GenerateInMemory = false;
        options.OutputAssembly = GeneratedName;
      }
      options.WarningLevel = 0;//0-4
      options.TreatWarningsAsErrors = false;
      options.CompilerOptions = "";
      options.TempFiles = new System.CodeDom.Compiler.TempFileCollection(".", false);
      string[] sources = new string[] { string.Format("namespace {0}_ns {{ public class {0}_type {{ }} }}", GeneratedName) };
      System.CodeDom.Compiler.CompilerResults results = provider.CompileAssemblyFromSource(options, sources);
      if (results.NativeCompilerReturnValue == 0)
      {
        result = results.CompiledAssembly;
      }
      else
      {
        var codelines = new System.Text.StringBuilder();
        foreach (System.CodeDom.Compiler.CompilerError err in results.Errors)
          codelines.AppendFormat("({0},{1}) {2}:{3} / ", err.Line, err.Column, err.ErrorNumber, err.ErrorText);
        throw new Exception(codelines.ToString());
      }
      return result;
    }

    private void Init(string name, TextReader textReader)
    {
      try
      {
        IsWellFormedXML = false;
        this.Name = name;
        doc = XDocument.Load(textReader);
        IsWellFormedXML = true;
        efferent = new List<Dependency>();
        doc.Root.Descendants(NS + "Reference").Aggregate(efferent, (whole, next) => { whole.Add(new Reference(next)); return whole; });
        doc.Root.Descendants(NS + "ProjectReference").Aggregate(efferent, (whole, next) => { whole.Add(new ProjectReference(next)); return whole; });
        doc.Root.Descendants(NS + "Content").Aggregate(efferent, (whole, next) => { whole.Add(new Content(next)); return whole; });
      }
      catch (System.Xml.XmlException)
      {
        //The specific supported use case will retry with another reader type different than XML.
      }
    }
  }
}